"""
主要是五种主Agent类的实现，可以模拟公司Firms 居民HouseHolds 银行Banks 政府Government 中央银行CentralBank的行为
Firm Agent的主要行为，包含两类公司C公司和K公司
- 生产计划（生产多少是如何决定的？)
- 贷款过程（C公司和K公司向银行贷款）
- 招聘计划(根据生产多少来决定招聘多少？)
- 销售过程（C公司与K公司互动买卖资本品，C公司与H互动买卖消费品）
- 利润核算过程
    - 预期利润 = 预期销售 - 工资

"""

from calendar import c
from csv import Error
from math import isnan
import random
import datetime
import re
import uuid
import logging
import pandas as pd
import numpy as np
from typing import Dict, List, Union, Optional
from utils import CentralBankInfo, GovernmentInfo, apt, prt, p, INI_YEAR
from utils import EpisodeItem, TransactionFlowItem
from utils import ensure_folder_exists
from utils import Record, FirmInfo, BankInfo, HouseholdInfo

from contract import DepositContract, LoanContract #存款合约
from contract import LoanContractCollection #贷款合约
from contract import BondContract # 债券合约
from contract import TreasuryDepositContract # 财政存款
from contract import StockHolderContract # 股权合约
from contract import JobContract # 工作合约
from contract import ReserveContract # 保证金合约
from contract import CashAdvanceContract # 现金预借合约
from contract import CapitalContract # 资本品合同
from contract import ConsumptionContract  # 消费品供需合同
from contract import ConsumptionGoods # 消费品
from contract import CapitalGoods # 资本品




class CapitalFirmProduceAndRecruit:
    """
    资本品公司的生产和招聘计划类，用于规划资本品公司在特定年份的生产和招聘策略。
    :param year: 当前年份，用于确定生产和招聘计划的时间范围。
    :param sales_last_period: 上一时期的实际销售额，作为制定本期计划的参考。
    :param expected_sales_last_period: 上一时期的预期销售额，与实际销售额对比，评估预测准确性。
    :param inventory_last_period_end: 上一时期结束时的库存数量，影响本期的生产决策。
    :param wages_last_period: 上一时期的工资支出，考虑成本因素对招聘和生产的影响。
    :param expected_wages_last_period: 上一时期的预期工资支出，用于对比实际支出，调整成本预算。
    :param mark_up: 加价率，用于确定产品的定价策略。
    """
    def __init__(
        self,
        year,
        sales_last_period,
        expected_sales_last_period,
        inventory_last_period_end,
        wages_last_period,
        expected_wages_last_period,
        mark_up,
        price_last_period,
        ):
        adp_v = p.a.adaptive_lambda
        self.year = year
        self.sales_last_period = sales_last_period
        self.expected_sales_last_period = expected_sales_last_period  # 第一年用不上

        # 第一步，形成预期
        if self.year == INI_YEAR:

            self.expected_sales_this_period = p.y.output_of_capital * \
                (1 + p.ss.steady_state_rate) / p.n.number_of_k_firms

            text_1_1 = f"S_t(expected):{self.expected_sales_this_period:.5f}="
            text_1_1 += f"{p.y.output_of_capital / p.n.number_of_k_firms} * {1 + p.ss.steady_state_rate}"
            self.info = {'1-1预期销售量': text_1_1}

        else:
            self.expected_sales_this_period = expected_sales_last_period + adp_v * \
                (self.sales_last_period - expected_sales_last_period)

            text_1_1 = f"S_t(expected):{self.expected_sales_this_period:.5f}="
            text_1_1 += f"上期预期销售{expected_sales_last_period} +"
            text_1_1 += f"适应度系数{adp_v} * (上期真实销售{self.sales_last_period:.6f} - "
            text_1_1 += f"上期预期销售{expected_sales_last_period:.6f})"
            self.info = {'1-1预期销售':text_1_1}

        # 第二步，计划生产
        self.inventory_last_period_end = inventory_last_period_end
        s = self.expected_sales_this_period * (1 + p.ratio.ratio_of_inventory_to_output) - self.inventory_last_period_end
        self.desired_output_this_period = max(s, 0) # 防止产出变为负值
        self.y_desired = self.desired_output_this_period
        self.info['1-2计划生产'] = f"y_d:{self.y_desired:.4f} = {self.expected_sales_this_period:.5f} * " \
                               f"(1+{p.ratio.ratio_of_inventory_to_output}) - {self.inventory_last_period_end:.5f} "
        # 第三步，需要工人
        self.worker_needed = int(self.y_desired / p.para.productivity_of_k_firm)
        if self.worker_needed < 0:
            print(666)
        self.info['1-3需要工人'] = f"w:{self.worker_needed} = {self.y_desired:.5f} / {p.para.productivity_of_k_firm}"
        ## 第四步，定价 （1 + 加成比例）（预期工资 * 预期工人数量 / 预期产量）
        self.wages_last_period = wages_last_period
        self.expected_wages_last_period = expected_wages_last_period  # 工资用，第一年用不上
        self.mark_up = mark_up

        if self.year == INI_YEAR:
            self.expected_wages_this_period = p.h.households_wage * (1 + p.ss.steady_state_rate)
            self.info['1-4预期平均工资'] = f"W(expected){self.expected_wages_this_period:.5f}" \
                                     f" = {p.h.households_wage} * " \
                                     f"1 + {p.ss.steady_state_rate:.5f}"
        else:
            self.expected_wages_this_period = self.expected_wages_last_period + adp_v * (
                    self.wages_last_period - self.expected_wages_last_period)
            self.info['1-4预期平均工资'] = f"W(expected) {self.expected_wages_this_period:.5f} = " \
                                     f"{self.expected_wages_last_period:.4f} + {adp_v} * " \
                                     f"({self.wages_last_period:.4f} - " \
                                     f"{self.expected_wages_last_period:.4f})"

        if self.worker_needed <= 0:
            self.worker_needed = 1
            self.this_year_price = price_last_period
        else:
            self.this_year_price = (1 + self.mark_up) * (self.expected_wages_this_period * self.worker_needed) / self.y_desired

        self.info['1-5价格形成'] = f"this_year_price:{self.this_year_price:.5f} = (1 + {self.mark_up}) * " \
                                  f"({self.expected_wages_this_period:.5f}" \
                                  f"*{self.worker_needed}) / {self.y_desired:.5f}"
        # 第五步，形成销售预期

    def print_info(self):
        for key, value in self.info.items():
            print(f"{key}:{value}")


class ConsumptionFirmProduceAndRecruit:
    def __init__(
        self,
        year,
        sales_last_period,
        expected_sales_last_period,
        inventory_last_period_end,
        wages_last_period,
        expected_wages_last_period,
        mark_up,
        capital_holds,
        price_last_period,
        ):
        adp_v = p.a.adaptive_lambda
        self.year = year
        self.sales_last_period = sales_last_period
        self.expected_sales_last_period = expected_sales_last_period
        self.price_last_period = price_last_period

        # 第一步，形成预期
        if self.year == INI_YEAR:
            self.expected_sales_this_period = p.y.output_of_consumption * \
                (1 + p.ss.steady_state_rate) / p.n.number_of_c_firms

            text_1_1 = f"S_t(expected):{self.expected_sales_this_period:.5f}="
            text_1_1 += f"({p.y.output_of_consumption} / {p.n.number_of_c_firms})"
            text_1_1 += f"{p.y.output_of_consumption / p.n.number_of_c_firms} * {1 + p.ss.steady_state_rate}"
            self.info = {'1-1预期销售量': text_1_1}

        else:
            self.expected_sales_this_period = self.expected_sales_last_period + \
                adp_v * (self.sales_last_period - self.expected_sales_last_period)

            text_1_1 = f"{self.year}年 > S_t(expected):{self.expected_sales_this_period:.2f}="
            text_1_1 += f"上期预期销售{expected_sales_last_period:2f} +"
            text_1_1 += f"适应度系数{adp_v} * (上期真实销售{self.sales_last_period:.2f} - "
            text_1_1 += f"上期预期销售{expected_sales_last_period:.2f})"

            self.info = {'1-1预期销售量':text_1_1}

        # 第二步，计划生产
        self.inventory_last_period_end = inventory_last_period_end
        self.desired_output_this_period = self.expected_sales_this_period * (
                1 + p.ratio.ratio_of_inventory_to_output) - self.inventory_last_period_end
        self.y_desired = self.desired_output_this_period # y_desired一定能实现么？
        # 第三步，需要工人
        if self.year == INI_YEAR:
            self.capital_holds = p.c.capital_of_consumption_firm / p.n.number_of_c_firms
        else:
            # 如何让第二年几乎等于第一年呢？ini
            self.capital_holds = capital_holds

        max_output_c_firm_can_meet = p.para.productivity_of_c_firm * self.capital_holds
        if self.y_desired > max_output_c_firm_can_meet:
            self.y_desired = max_output_c_firm_can_meet
            self.desired_output_this_period = max_output_c_firm_can_meet

        if self.y_desired < 0:
            self.y_desired = 0

        self.cal_utility = self.y_desired / (p.para.productivity_of_c_firm * self.capital_holds)
        self.worker_needed = int(self.y_desired / (p.ratio.labor_capital_ratio * p.para.productivity_of_c_firm))
        self.desired_capacity_utilization = self.cal_utility

        self.mark_up = mark_up
        ##  第四步，定价 （1 + 加成比例）（预期工资 * 预期工人数量 / 预期产量）
        self.wages_last_period = wages_last_period
        self.expected_wages_last_period = expected_wages_last_period  # 工资用，第一年用不上
        if self.year == INI_YEAR:
            self.expected_wages_this_period = p.h.households_wage * (1 + p.ss.steady_state_rate)  # 第一年直接增长
        else:
            self.expected_wages_this_period = self.expected_wages_last_period + adp_v * (
                    self.wages_last_period - self.expected_wages_last_period)

        if self.worker_needed <= 0:
            self.worker_needed = 1
            self.this_year_price = self.price_last_period
        else:
            self.this_year_price = (1 + self.mark_up) * (self.expected_wages_this_period * self.worker_needed) / self.y_desired


    def print_info(self):
        for key, value in self.info.items():
            print(f"{key}:{value}")


class Firms:
    def __init__(self, ini_id, firm_type, ini_inventory, ini_loans, price_and_recruit, inventory):
        # 基本属性
        self.year = INI_YEAR  # check
        self.tf_item = TransactionFlowItem()
        self.ep_item = EpisodeItem()
        self.firm_id = ini_id  # check
        self.firm_type = firm_type  # ['Capital Goods', 'Consumer Goods']
        self.yearly_info = {INI_YEAR: FirmInfo(ini_id, ini_inventory)}
        self.yearly_info_text = {INI_YEAR:{}}
        # 各类合约
        self.deposits_contracts: DepositContract = DepositContract({})  # 存款合同，获取利息，支付账户
        self.stockholder_contract: Dict[str, StockHolderContract] = {}  # 股东合同，发股利用
        self.job_contracts_collection: Dict[str, JobContract] = {}  # 就业合同，发工资用
        # 销售合同（ 关于资本品）
        self.capital_goods_contracts_collection: Dict[str, CapitalContract] = {}  # K公司的资本品合同集合，字典，key为C公司id
        # 销售合同（ 关于消费品）
        self.consumption_goods_contracts_collection: Dict[str, ConsumptionContract] = {}  # C公司与H签订的合同集合
        self.price_and_recruit: Union[CapitalFirmProduceAndRecruit, ConsumptionFirmProduceAndRecruit] = price_and_recruit
        # 行为分类
        self.item = EpisodeItem()
        self.tf_item = TransactionFlowItem()
        self.markup = price_and_recruit.mark_up
        self.cum_profit_loss = 0
        # 价格
        if self.firm_type == 'Capital Goods':
            self.identity = 'k_firm'
            self.is_cap_firm = True
            self.capital_inventory:CapitalGoods = inventory
            self.inventory = self.capital_inventory
        else:
            self.identity = 'c_firm'
            self.is_cap_firm = False
            consumption_capital = self.ini_batch_of_capital()
            self.price_and_recruit.capital_holds = consumption_capital
            self.batch_of_capital = consumption_capital  # 只有消费品公司才会把资本品作为生产资料
            self.consumption_inventory:ConsumptionGoods = inventory
            self.inventory = self.consumption_inventory
        # 贷款
        self.batch_of_loan_contracts: LoanContractCollection = LoanContractCollection([LoanContract({})])  # 贷款
        self.payment_of_loans_list = self.ini_payment_of_loans_list(ini_loans)  # 公司在历史不同时期的贷款
        self.ep = EpisodeItem()
        self.log: list = []
        self.df_year_log = {}
        self.balance_sheet_table = pd.DataFrame()
        self.transaction_flow_table = pd.DataFrame()
        self.check_deposit_table = pd.DataFrame()
        self.balance_sheet_info = {}

    def id_(self):
        return self.firm_id

    def pay(self, money: float, record: Record):
        """
        公司用存款付钱,并记录到日志
        """
        if self.deposits_contracts.amount < money:
            self.save_self_history_log()
            print(self.yearly_info[self.year])
            print(self.firm_id)
            print(666)
            
        record.deposit_before = self.deposits_contracts.amount
        assert self.deposits_contracts.amount >= money
        
        record.deposit_before = self.deposits_contracts.amount
        self.deposits_contracts.amount -= money
        record.deposit_after = self.deposits_contracts.amount

        record.time = datetime.datetime.now()

        self.log.append(record)

    def accept_payment(self, money: float, record: Record):
        """
        公司接受对方付款，存款账户增加，并记录到日志
        """
        record.deposit_before = self.deposits_contracts.amount
        self.deposits_contracts.amount += money
        record.deposit_after = self.deposits_contracts.amount
        record.time = datetime.datetime.now()
        self.log.append(record)

    def sell(self, quantity: Union[int, float], record: Record):
        """
        公司卖产品【消费品，资本品】给付款方，库存减少
        """
        assert self.inventory.quantity >= quantity
        record.deposit_before = self.deposits_contracts.amount
        self.inventory.quantity -= quantity
        record.deposit_after = self.deposits_contracts.amount
        self.log.append(record)

    def buy_capital(self, quantity: Union[int, float], record: Record, price: Union[int, float]):
        """
        消费品公司购买资本品
        """
        assert not self.is_cap_firm  # 必须是消费品公司才能购买资本品
        self.batch_of_capital.append(CapitalGoods(year=self.year, quantity=quantity, price=price, share=1))
        record.deposit_after = self.deposits_contracts.amount
        self.log.append(record)

    def recent_five_year_utility_rate(self):
        if self.year == INI_YEAR:
            return 1
        else:
            utility_rate_li = [info.cal_utility for _, info in self.yearly_info.items()]
            n = len(utility_rate_li)
            if n<= 6:
                return np.average(utility_rate_li)
            else:
                return np.average(utility_rate_li[-5:])

    def get_bs_info(self):
        dep_as_assets = self.deposits_contracts.amount
        loans_as_debt = self.batch_of_loan_contracts.cal_loans_as_debt()
        inventory_mv = self.inventory.quantity * self.inventory.price
        if self.is_cap_firm:
            capital_as_assets = 0
        else:
            capital_sum = self.cal_batch_of_capital_with_price()
            capital_as_assets = capital_sum
        return dep_as_assets, loans_as_debt, capital_as_assets, inventory_mv

    def ensure_dir_before_save(self):
        dir_ = p.ctrl.log_dir
        ensure_folder_exists(dir_)
        dir_h = f"{dir_}{self.id_()}/"
        ensure_folder_exists(dir_h)
        return dir_h

    def save_log(self):
        dir_h = self.ensure_dir_before_save()
        df = pd.DataFrame(self.log)
        df.to_csv(f"{dir_h}{self.year}.csv", index=False)

    def reset_state(self, save_log=False):
        self.yearly_info_text[self.year-1] = self.price_and_recruit.info

        dep_as_assets, loans_as_debt, capital_as_assets, inventory_mv = self.get_bs_info()
        self.balance_sheet_info['deposits_start'] = dep_as_assets
        self.balance_sheet_info['loans_start'] = loans_as_debt
        self.balance_sheet_info['capital_start'] = capital_as_assets
        self.balance_sheet_info['inventory_mv_start'] = inventory_mv

        if save_log:
            self.save_log()

        self.log = []

    def end_state_update(self):
        """
        更新公司信息，用于下一期计算
        包括：

        """
        info = self.yearly_info[self.year]
        info.average_wages_this_period = np.average([jb_c.wage for _, jb_c in self.job_contracts_collection.items()])  # 下个循环有用的数据

        if self.firm_type == "Consumer Goods":
            s_qty = np.sum([d.quantity for d in self.log if d.item == self.ep.goods_flow_consumption])
            s_amount = np.sum([d.amount for d in self.log if d.item == self.ep.cash_flow_consumption])
        else:
            s_qty = np.sum([d.quantity for d in self.log if d.item == self.ep.goods_flow_capital])
            s_amount = np.sum([d.amount for d in self.log if d.item == self.ep.cash_flow_capital])

        info.sales_this_period_quantity_at_end_state_update = s_qty
        info.sales_this_period_amount_at_end_state_update = s_amount
        # 如果有亏损，记录累积起来，盈利首先要弥补亏损再发股利
        profits = self.realized_profits()
        self.cum_profit_loss += profits
        # 情形二：如果累计亏损为负值，也不发股利，期末改变累计亏损，这里要在每一期末计算下累计亏损
        self.cum_profit_loss = min(0, self.cum_profit_loss)

    def produce_plan_pricing_and_cal_need_workers(self):
        # 本函数实现的全是规划的（公司站 在年初的展望，不是真实的）
        if self.year == INI_YEAR:
            expected_sales_last_period = 0
            expected_wages_last_period = 0
            wages_last_period = 0
            sales_last_period = 0
            price_last_period = 0
        else:
            # 加载上期信息集合
            last_year_info = self.yearly_info[self.year - 1]

            expected_sales_last_period = last_year_info.expected_sales_this_period #本期要在最后更新
            expected_wages_last_period = last_year_info.expected_wages_this_period #本期要在最后更新
            wages_last_period = last_year_info.average_wages_this_period # 真实的流量
            sales_last_period = last_year_info.sales_this_period_quantity_at_end_state_update # 真实的流量

            rd_value = np.random.normal(p.a.normal_d_mean, p.a.normal_d_dev)  # 得到一个随机
            # 加成是内生通过上期数据自适应确定的，当公司上期库存销售比高于预期，那么其利润加成就下调，当公司上期库存销售比低于预期，那么其利润加成
            # 上升 注意：定价的时候还没有形成真实工资，所以采用了预期平均工资
            inv_ratio_last_period = self.inventory.quantity / sales_last_period
            price_last_period = last_year_info.price
            if inv_ratio_last_period <= p.ratio.ratio_of_inventory_to_output:
                self.markup = self.markup * (1 + rd_value)
            else:
                self.markup = self.markup * (1 - rd_value)

        if self.is_cap_firm:
            self.price_and_recruit = CapitalFirmProduceAndRecruit(
                year=self.year,
                sales_last_period=sales_last_period,
                expected_sales_last_period=expected_sales_last_period,
                inventory_last_period_end=self.inventory.keep_quantity,
                expected_wages_last_period=expected_wages_last_period,
                wages_last_period=wages_last_period,
                mark_up=self.markup,
                price_last_period=price_last_period,
                )
        else:
            self.price_and_recruit = ConsumptionFirmProduceAndRecruit(
                year=self.year,
                sales_last_period=sales_last_period,
                expected_sales_last_period=expected_sales_last_period,
                inventory_last_period_end=self.inventory.keep_quantity,
                capital_holds=self.cal_batch_of_capital(),
                expected_wages_last_period=expected_wages_last_period,
                wages_last_period=wages_last_period,
                mark_up=self.markup,
                price_last_period=price_last_period,
                )
            # 对于消费品来说，定价可能有问题。
        # 重新混合了价格
        sum_q = self.price_and_recruit.y_desired + self.inventory.quantity
        ratio_this_year = self.price_and_recruit.y_desired / sum_q
        price_this_year = self.price_and_recruit.this_year_price
        ratio_history = self.inventory.quantity / sum_q
        price_history = self.inventory.price

        self.price_and_recruit.price = ratio_this_year * price_this_year + ratio_history * price_history
        self.inventory.price = self.price_and_recruit.price # 一旦做了计划，库存按照最新报价

        firm_info = FirmInfo(firm_id=self.id_(), start_inv=self.inventory.quantity)
        # 记录预期销量
        firm_info.expected_sales_this_period = self.price_and_recruit.expected_sales_this_period
        # 记录上期末库存
        firm_info.end_state_quantity_of_inventory_last_period = self.inventory.keep_quantity
        # 记录本期产量
        firm_info.desired_output_this_period = self.price_and_recruit.y_desired
        if not self.is_cap_firm:
            # 计算产能利用率
            firm_info.cal_utility = self.price_and_recruit.cal_utility
            firm_info.desired_capacity_utility = self.price_and_recruit.desired_capacity_utilization
        # 计算预期工资
        firm_info.worker_needed = self.price_and_recruit.worker_needed
        firm_info.mark_up = self.price_and_recruit.mark_up
        firm_info.price = self.price_and_recruit.price
        firm_info.expected_wages_this_period = self.price_and_recruit.expected_wages_this_period
        firm_info.expected_sales_last_period = self.price_and_recruit.expected_sales_last_period
        self.yearly_info[self.year] = firm_info
        self.yearly_info[self.year].workers_gap = 0 # 为了后面如果招工不够，需要显示出来缺口

    def decision_history_df(self):
        return pd.DataFrame(self.yearly_info)

    @staticmethod
    def ini_payment_of_loans_list(ini_loans):
        total_loans = ini_loans
        latest_loans = p.para.factor_of_batch_loan * total_loans
        d = p.para.duration_or_loan
        ss = p.ss.steady_state_rate
        # 贷款d年，利率为ss,计算每年还款额
        return [latest_loans * (d - i) / (d * (1 + ss) ** i) for i in range(int(d))]

    def ini_batch_of_capital(self):
        """
        初始化资本品
        :return:
        """
        assert not self.is_cap_firm
        batch_capital = []
        arg_v = 0
        for t in range(1, int(p.para.duration_or_capital) + 1):
            arg_v += t / (p.para.duration_or_capital * (1 + p.ss.steady_state_rate) ** (p.para.duration_or_capital - t))

        k = p.c.capital_of_consumption_firm / p.n.number_of_c_firms

        p_k = p.p.price_of_capital
        for t in range(1, int(p.para.duration_or_capital) + 1):
            arg_v = t / p.para.duration_or_capital
            p_capital = p_k / (1 + p.ss.steady_state_rate) ** (p.para.duration_or_capital - t)
            share = arg_v
            capital_t = CapitalGoods(
                year=INI_YEAR - (p.para.duration_or_capital - t + 1),
                quantity=k / p.para.duration_or_capital,
                price=p_capital,
                share=share
                )
            batch_capital.append(capital_t)
        return batch_capital

    def cal_batch_of_capital(self):
        # 这里应该乘以share，因为折旧过程中，资本quantity不变，更多体现在share递减，
        # 当share减小到一定程度，capitalgood删除掉
        c_goods_value_sum = 0
        for c_goods in self.batch_of_capital:
            v = c_goods.quantity # * c_goods.share
            c_goods_value_sum += v
        return c_goods_value_sum

    def cal_batch_of_capital_with_price(self):
        # 计算消费品公司持有多少资本
        # 目前有两处用到，第一次是监测资本存量，第二次是计算投资，第三次计算资产负债表
        c_goods_value_sum = 0
        for c_goods in self.batch_of_capital:
            # v = c_goods.price * c_goods.quantity * c_goods.share
            v = c_goods.quantity * c_goods.share * c_goods.price
            c_goods_value_sum += v
        return c_goods_value_sum
    
    def cal_batch_of_capital_without_price(self):
        c_goods_value_sum = 0
        for c_goods in self.batch_of_capital:
            v = c_goods.quantity * c_goods.share
            c_goods_value_sum += v
        return c_goods_value_sum

    @property
    def cal_payment_of_wages(self):
        v = np.sum([jb_c.wage for _, jb_c in self.job_contracts_collection.items()])
        self.price_and_recruit.info['2-5预期总工资'] = f"{v}:{len(self.job_contracts_collection)}个员工（调用cal_payment_of_wages）"
        return v

    @property
    def cal_interests_payment_of_loans(self):
        v = sum([contract.amount * contract.interest_rate for contract in self.batch_of_loan_contracts])

        text_2_4 = f"{v:.3f} = 贷款笔数：{len(self.batch_of_loan_contracts)} >>>"
        if len(self.batch_of_loan_contracts) > 0:
            text_2_4 += f"贷款利率：{self.batch_of_loan_contracts[-1].interest_rate}"
        self.price_and_recruit.info['2-4预期贷款利息'] = text_2_4
        
        return v

    @property
    def cal_principle_payment_of_loans(self):
        info = self.yearly_info[self.year]
        if hasattr(info, 'principle_repayment_of_loans'):
            return info.principle_repayment_of_loans
        else:
            v = sum([contract.amount for contract in self.batch_of_loan_contracts if contract.left_year < 2])
            info.principle_repayment_of_loans = v
            return v

    def unit_cost(self):
        """
        单位成本等于工资成本加上资本摊销除以产量
        :return:
        """
        if self.price_and_recruit.y_desired == 0:
            print(666)
        unit_cost = self.cal_payment_of_wages / self.price_and_recruit.y_desired
        f_unit_cost = f"{unit_cost} = {self.cal_payment_of_wages:.5f} / {self.price_and_recruit.y_desired:.5f}"
        self.price_and_recruit.info['1-6单位成本'] = f_unit_cost
        return unit_cost

    def cal_capital_amortization(self):
        """
        资本摊销,调用意味着摊销了
        这里包含名义价格
        :return:
        """
        assert self.firm_type == "Consumer Goods"
        if self.year == INI_YEAR:
            return p.c.amortization_of_consumption_firms / p.n.number_of_c_firms
        else:
            ini_amortization = 0
            for batch_capital in self.batch_of_capital:
                temp_amt = batch_capital.quantity * batch_capital.price / p.para.duration_or_capital
                ini_amortization += temp_amt
            return ini_amortization

    def cal_capital_amortization_without_price(self):
        """
        资本摊销,调用意味着摊销了，改变了资本状态，只能调用一次

        :return:
        """
        assert self.firm_type == "Consumer Goods"
        if self.year == INI_YEAR:
            return p.c.amortization_of_consumption_firms / (p.n.number_of_c_firms * p.p.price_of_capital)
        else:
            ini_amortization = 0
            for batch_capital in self.batch_of_capital:
                temp_amt = batch_capital.quantity / p.para.duration_or_capital
                ini_amortization += temp_amt
            return ini_amortization

    def execute_capital_amortization(self, execute=False):
        """
        处理真实的资产折旧,由于调用比较频繁，execute只能调用一次
        折旧金额计入日志
        """
        assert not self.is_cap_firm
        # 如果不执行且已经计算过一次，直接输出
        if not execute:
            if hasattr(self.yearly_info[self.year], 'capital_amortization'):
                return self.yearly_info[self.year].capital_amortization
        # 如果执行，或没有计算过，继续
        
        # 累加器
        amt_all = 0
        quantity_all = 0
        
        for i in range(len(self.batch_of_capital)):
            # 这一份资本是哪一年出现的
            capital_year = int(self.batch_of_capital[i].year)
            if self.year != capital_year:
                # 计提折旧份额
                share_min = 1 / p.para.duration_or_capital
                # 这一批资本的数量
                quantity = self.batch_of_capital[i].quantity
                # 这一批资本的价格
                price = self.batch_of_capital[i].price
                # 折旧数量
                quantity_of_batch_amt = quantity * share_min
                # 折旧金额
                price_of_batch_amt = price * quantity * share_min
                # 累计折旧
                amt_all += price_of_batch_amt
                quantity_all += quantity_of_batch_amt
                if execute:
                    self.batch_of_capital[i].share -= 1 / p.para.duration_or_capital

        if execute:
            # 如果最后折旧和之前不一样，也有问题，
            assert self.yearly_info[self.year].capital_amortization == amt_all
        else:
            self.yearly_info[self.year].capital_amortization = amt_all
        
        for i in range(len(self.batch_of_capital)):
            if self.batch_of_capital[0].share <= 0.001:
                del self.batch_of_capital[0]

        if execute:
            record_dic = {
                'payer': self.id_(),
                'amount': amt_all,
                'quantity': quantity_all,
                'item': self.item.capital_amortization,
                'transaction_flow_item': self.tf_item.Capital_amortization,
                'uuid': uuid.uuid4(),
            }
            record = Record(uuid.uuid4())
            record.add_more_attr(record_dic)
            self.log.append(record)
        
        return self.yearly_info[self.year].capital_amortization

    def expected_variation_of_inventories(self):
        """
        预期的存货变动，此时存货还没有真正减少，
        因此期末存货 等于 当前存货 + 预期产量 - 预期销量
        预期存货变化既包含量，也包含价
        """
        if self.year == INI_YEAR:
            start_inv = self.yearly_info[self.year].start_inv
            ini_unit_cost = {'c_firm': p.c.unit_cost_of_consumption_firm, 'k_firm': p.k.unit_cost_of_capital_firm}
            unit_cost_last_period = ini_unit_cost[self.identity]
        else:
            last_year_info = self.yearly_info[self.year - 1]
            start_inv = last_year_info.end_inv
            unit_cost_last_period = last_year_info.unit_cost_this_period

        # 【量】 预期期末存货 = 期初存货 + 预期产量 - 预期销量
        end_inv = start_inv + self.price_and_recruit.y_desired - self.price_and_recruit.expected_sales_this_period

        variation_of_inv = end_inv * self.unit_cost() - start_inv * unit_cost_last_period

        self.yearly_info[self.year].expected_variation_of_inv = variation_of_inv

        text_2_3 = f"{variation_of_inv:.4f} = {end_inv:.4f} * {self.unit_cost():.4f} - {start_inv:.4f} * "
        text_2_3 += f"{unit_cost_last_period:.4f}"

        self.price_and_recruit.info['2-3预期存货变动'] = text_2_3
        return variation_of_inv

    def nominal_variation_of_inventories(self):
        """
        计算真实的存货估值的变动
        :return:
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'nominal_variation_of_inventories'):
            unit_cost_this_period = self.unit_cost()
            end_inv = self.inventory.quantity
            start_inv = self.inventory.keep_quantity
            if self.year == INI_YEAR:
                ini_unit_cost = {'c_firm': p.c.unit_cost_of_consumption_firm, 'k_firm': p.k.unit_cost_of_capital_firm}
                unit_cost_last_period = ini_unit_cost[self.identity]
            else:
                unit_cost_last_period = self.yearly_info[int(self.year - 1)].unit_cost_this_period

            this_year_info = self.yearly_info[self.year]

            if not hasattr(this_year_info, 'unit_cost_this_period'):
                this_year_info.unit_cost_this_period = unit_cost_this_period
            if not hasattr(this_year_info, 'end_inv'):
                this_year_info.end_inv = end_inv

            variation_of_inv = end_inv * unit_cost_this_period - start_inv * unit_cost_last_period
            this_year_info.variation_of_inv = variation_of_inv

            text_3_3 = f"{variation_of_inv:.2f} = {end_inv:.2f} * {unit_cost_this_period:.2f} - {start_inv:.4f} * "
            text_3_3 += f"{unit_cost_last_period:.4f}"
            self.price_and_recruit.info['3-3真实存货变动'] = text_3_3
        
            self.yearly_info[self.year].nominal_variation_of_inventories = variation_of_inv

        return self.yearly_info[self.year].nominal_variation_of_inventories

    def expected_profits(self):
        """
                C公司：销售收入 + 存款利息 + 存货估值变动 - 工资支出 - 贷款利息支出 - 资本摊销
                K公司：销售收入 + 存款利息 + 存货估值变动 - 工资支出 - 贷款利息支出
                :return:
        """
        expected_nvi = self.expected_variation_of_inventories()

        inflow_li = [
            self.price_and_recruit.expected_sales_this_period * self.price_and_recruit.price,
            self.deposits_contracts.interest_rate * self.deposits_contracts.amount, 
            expected_nvi
        ]
        outflow_li = [
            self.cal_interests_payment_of_loans, self.cal_payment_of_wages
            ]
        profits = sum(inflow_li) - sum(outflow_li)

        if self.is_cap_firm:
            after_amt = profits
        else:
            amt = self.execute_capital_amortization()
            after_amt = profits - amt  # 消费品公司需要资本摊销,这里包含价格

        self.yearly_info[self.year].expected_profits = after_amt

        text_2_1 = f"{inflow_li[0]:.5f} = {self.price_and_recruit.expected_sales_this_period:.5f} * "
        text_2_1 += f"{self.price_and_recruit.price:.5f}"
        self.price_and_recruit.info['2-1预期销售额'] = text_2_1

        text_2_5 = f"{inflow_li[1]:.2f} = {self.deposits_contracts.interest_rate} * {self.deposits_contracts.amount:.2f}"
        self.price_and_recruit.info['2-2预期存款利息'] = text_2_5
            

        text_2_6 = f"profits: {after_amt:.4f} = （2-1）预期销售额{inflow_li[0]:.5f} +（2-2）存款利息 {inflow_li[1]:.5f} +"
        text_2_6 += f"（2-3）存货变动 {expected_nvi:.5f} - （2-4）贷款利息 {outflow_li[0]:.5f} - "
        
        if self.is_cap_firm:
            text_2_6 += f"（2-5）工资支出 {outflow_li[1]:.5f}"
        else:
            text_2_6 += f"（2-5）总工资 {outflow_li[1]:.5f} - 折旧或摊销 {amt:.5f}"
        self.price_and_recruit.info['2-6预期利润'] = text_2_6

        return after_amt

    def realized_profits(self):
        """
        计算公司利润
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'realized_profits'):
            sales_item = self.ep.cash_flow_capital if self.is_cap_firm else self.ep.cash_flow_consumption
            dep_int_item = self.ep.bank_pay_dep_int_to_k if self.is_cap_firm else self.ep.bank_pay_dep_int_to_c
            loan_int_item = self.ep.k_firm_pay_loan_int if self.is_cap_firm else self.ep.c_firm_pay_loan_int
            wage_item = self.ep.k_firm_pay_wage if self.is_cap_firm else self.ep.c_firm_pay_wage

            # sales_quantity_this_period = np.sum(list(d.quantity for d in self.log if d.item == sales_item))
            sales_this_period = np.sum(list(d.amount for d in self.log if d.item == sales_item))
            dep_int_received_this_period = np.sum(list(d.amount for d in self.log if d.item == dep_int_item))
            nvi = self.nominal_variation_of_inventories()

            interest_payment_of_loans = np.sum(list(d.amount for d in self.log if d.item == loan_int_item))
            payment_of_wages = np.sum(list(d.amount for d in self.log if d.item == wage_item))

            profits = sales_this_period + dep_int_received_this_period + nvi - interest_payment_of_loans - payment_of_wages

            if self.is_cap_firm:
                after_amt = profits
                amt = 0
            else:
                amt = self.execute_capital_amortization()
                after_amt = profits - amt  # 消费品公司需要资本摊销
                self.price_and_recruit.info['3-51真实折旧'] = f"amt: {amt:.4f}"

            self.yearly_info[self.year].sales_this_period = sales_this_period
            self.yearly_info[self.year].profits = after_amt
            self.price_and_recruit.info['3-1真实销售额'] = f"{sales_this_period:.4f} {sales_item}"
            self.price_and_recruit.info['3-2真实存款利息'] = f"{dep_int_received_this_period:.4f} {dep_int_item}"
            # 3-3
            self.price_and_recruit.info['3-4真实存款利息'] = f"{interest_payment_of_loans:.4f} {loan_int_item}"
            self.price_and_recruit.info['3-5真实支付工资'] = f"{payment_of_wages:.4f} {wage_item}"

            profits_info_text = f"{after_amt:.4f} = 销售额 {sales_this_period:.4f} " \
                                f"+ 存款利息 {dep_int_received_this_period:.4f} " \
                                f"+ 存货变动 {nvi:.4f} " \
                                f"- 贷款利息 {interest_payment_of_loans:.4f} " \
                                f"- 工资 {payment_of_wages:.4f}"

            if self.is_cap_firm:
                self.price_and_recruit.info['3-6真实利润'] = profits_info_text
            else:
                self.price_and_recruit.info['3-52真实折旧'] = f"amt折旧或资本摊销: {amt:.4f}"
                self.price_and_recruit.info['3-6真实利润'] = profits_info_text + f" - 资本摊销 {amt:.4f}"
            
            info.realized_profits = after_amt

        return info.realized_profits

    def expected_dividends(self):
        d = max(0, p.ratio.ratio_of_dividends_to_profits_left * self.expected_profits() * (1 - p.ratio.tax_rate))
        self.yearly_info[self.year].expected_dividends = d
        return d

    def expected_tax(self):
        exp = self.expected_profits()
        if exp > 0:
            return exp * p.ratio.tax_rate
        else:
            return 0

    def real_dividends(self):
        """
        实现的利润为 self.realized_profits()
        税后利润为 self.realized_profits() * (1 - p.ratio.tax_rate)
        计算真实的分红,分红比例为 p.ratio.ratio_of_dividends_to_profits_left
        分红给股东的钱 = 实现的利润 * 分红比例 * （1 - 税率）
        注意，这里不能负值强行变为零
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'real_dividends'):
            if self.cum_profit_loss < 0:
                div = 0
            else:
                realized_profits = self.realized_profits()
                if realized_profits > 0:
                    after_tax_profits = realized_profits * (1-p.ratio.tax_rate)
                else:
                    after_tax_profits = 0
                ratio = p.ratio.ratio_of_dividends_to_stockholder_as_share_of_profits_of_ck_firm
                div = after_tax_profits * ratio
            
            info.real_dividends = div

        return self.yearly_info[self.year].real_dividends

    def cal_dividends(self):
        """
        计算应发股利
        情形一：如果本期亏损，不发股利
        情形二：如果累计亏损为负值，也不发股利，期末改变累计亏损
        """
        realized_profits = self.realized_profits()
        if realized_profits < 0:
            return 0
        if self.cum_profit_loss < 0:
            return 0
        ratio = p.ratio.ratio_of_dividends_to_profits_left
        return realized_profits * (1-p.ratio.tax_rate) * ratio
            
    def keep_dividends(self):
        """
        实现的利润为 self.realized_profits()
        税后利润为 self.realized_profits() * (1 - p.ratio.tax_rate)
        计算真实的分红,分红比例为 p.ratio.ratio_of_dividends_to_profits_left
        分红给股东的钱 = 实现的利润 * 分红比例 * （1 - 税率）
        留存给公司的钱 = 实现的例如 * （1 - 分红比例）* （1 - 税率）
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'keep_dividends'):
            info.keep_dividends = self.cal_dividends()
        return self.yearly_info[self.year].keep_dividends

    def expected_ocf(self):
        """
        预期现金流 = 预期税后利润 + 折旧（摊销） - 存货变动 - 贷款本金支出
        """
        # 从log中看出这儿可能有问题，首先是expected profits， 和真实的profits
        if not hasattr(self.yearly_info[self.year], 'expected_ocf'):
            
            expected_profits = self.expected_profits()
            expected_nvi = self.expected_variation_of_inventories()
            principle_payment_of_loans = self.cal_principle_payment_of_loans
            
            if expected_profits <= 0:
                ocf = expected_profits - expected_nvi - principle_payment_of_loans
            else:
                ocf = expected_profits * (1 - p.ratio.tax_rate) - expected_nvi - principle_payment_of_loans
            
            if not self.is_cap_firm:
                ocf += self.execute_capital_amortization()  # 消费品公司需要在现金流中加回资本摊销

            self.yearly_info[self.year].expected_ocf = ocf
            
            if expected_profits <= 0:
                text_2_7 = f"{ocf:.4f} = 预期利润 {expected_profits:.4f} - "
            else:
                text_2_7 = f"{ocf:.4f} = 预期利润 {expected_profits:.4f} * {1 - p.ratio.tax_rate:.4f} - " 
            
            text_2_7 += f"预期存货变化 {expected_nvi:.4f} - 还贷款 {principle_payment_of_loans:.2f}"
            self.price_and_recruit.info['2-7预期现金流'] = text_2_7
            
        return self.yearly_info[self.year].expected_ocf

    def real_ocf(self):
        if not hasattr(self.yearly_info[self.year], 'real_ocf'):
            
            real_profits = self.realized_profits()
            nvi = self.nominal_variation_of_inventories()
            loan_repayment = self.cal_principle_payment_of_loans
            
            if real_profits <= 0:
                ocf = real_profits - nvi - loan_repayment
            else:
                ocf = real_profits * (1 - p.ratio.tax_rate) - nvi - loan_repayment
            
            if not self.is_cap_firm:
                ocf += self.execute_capital_amortization()
            
            self.yearly_info[self.year].real_ocf = ocf

            if real_profits <= 0:
                text_2_7 = f"{ocf:.4f} = 真实利润 {real_profits:.4f} - "
            else:
                text_2_7 = f"{ocf:.4f} = 真实利润 {real_profits:.4f} * {1 - p.ratio.tax_rate:.4f} - " 
            
            text_2_7 += f"存货变化 {nvi:.4f} - 还贷款 {loan_repayment:.2f}"
            self.price_and_recruit.info['2-7真实现金流'] = text_2_7
        
        return self.yearly_info[self.year].real_ocf

    def investment(self):
        """
        公司资产的目标跟收益率只差成正比（如果本期收益率大于上期收益率，那么就要求资产有一个增速），具体的投资额为期末和期初投资额之差加上摊销的
        部分。
        只能调用一次
        :return:
        """
        # 只有消费品公司才会投资
        assert self.firm_type == 'Consumer Goods'
        info = self.yearly_info[self.year]
        if not hasattr(info, 'investment'):
            # 公司本身持有的资本
            firm_holds_capital = self.cal_batch_of_capital_with_price()  # 不包含价格的K
            # 公司的投资额是怎样确定的？
            if self.year == INI_YEAR:
                capital_growth = p.ss.steady_state_rate
                text_capital_growth = f"capital_growth: {capital_growth:.2f}"
            else:
                # expected_ocf = self.expected_ocf()
                last_period_ocf = self.yearly_info[self.year-1].real_ocf
                rate_of_return = last_period_ocf / firm_holds_capital
                diff_of_rate = rate_of_return - p.k.target_profit_rate

                cap_uti_desired = self.price_and_recruit.desired_capacity_utilization
                cap_uti_target = p.k.target_capacity_utilization

                r1 = p.a.profit_rate_weight * diff_of_rate / p.k.target_profit_rate
                r2 = p.a.capacity_utilization_rate_weight * (cap_uti_desired - cap_uti_target) / cap_uti_target
                capital_growth = r2 + r1  # p384
                
                text_capital_growth = f"{capital_growth} = r1({r1:.6f}) + r2 ({r2:.6f}))\n"
                text_capital_growth += f"r1 = {p.a.profit_rate_weight} * {diff_of_rate} / {p.k.target_profit_rate}\n"
                text_capital_growth += f"r2 = {p.a.capacity_utilization_rate_weight} * ({cap_uti_desired}-{cap_uti_target})"
                text_capital_growth += f"/ {cap_uti_target}"

                self.price_and_recruit.info['2-8持有资本收益率'] = f"{rate_of_return:.6f}"
                self.price_and_recruit.info['2-9持有收益率与目标收益率之差'] = f"{diff_of_rate:.6f}"

            self.price_and_recruit.info['3-51资本增长'] = text_capital_growth

            target_capital = firm_holds_capital * (1 + capital_growth)
            amt = self.cal_capital_amortization()
            investment_with_price = max(0, target_capital - firm_holds_capital + amt)
            
            text_i_p = f"max(0,targte {target_capital} - holds {firm_holds_capital} + amt {amt})"
            self.price_and_recruit.info['3-52投资'] = text_i_p
            average_price = np.average([c.price for c in self.batch_of_capital])
            investment = investment_with_price / average_price

            text_i_pp = f"investment {investment} =  {investment_with_price} / {average_price} price"
            self.price_and_recruit.info['3-53投资'] = text_i_pp
                
            self.yearly_info[self.year].investment = investment
        
        return self.yearly_info[self.year].investment

    def capital_goods_amount(self):
        assert not self.is_cap_firm
        return np.sum([c.price * c.quantity for _, c in self.capital_goods_contracts_collection.items()])

    def history_div_gap(self):
        if self.year > INI_YEAR + 30:
            gap = [v.real_dividends - v.expected_dividends for year, v in self.yearly_info.items() if INI_YEAR < year < self.year]
            return max(gap)
        else:
            return 0

    def c_finance(self):
        if self.year == INI_YEAR:
            return 0
        if not hasattr(self.yearly_info[self.year], 'finance'):
            # 计算c公司的融资额，要以合同计算，而非预期
            deposits_holds = self.deposits_contracts.amount # 现在有多少存款
            # 第一关购买资本品后存款为正
            investment_amount = np.sum([c.quantity * c.price for _, c in self.capital_goods_contracts_collection.items()])
            finance_1 = investment_amount - deposits_holds + 1
            # 第二关，贷款本金利息偿还后为正
            consumption_amount = np.sum([c.quantity * c.price for _, c in self.consumption_goods_contracts_collection.items()])
            loan_int_pri_repayment = self.cal_interests_payment_of_loans + self.cal_principle_payment_of_loans
            finance_2 = finance_1 - consumption_amount + loan_int_pri_repayment
            # 第三关，获得存款利息，同时交税，发股利，发工资后为正
            deposit_interests = self.deposits_contracts.interest_rate * self.deposits_contracts.amount
            wages = np.sum([jb.wage for _,jb in self.job_contracts_collection.items()])
            div = self.expected_dividends()
            tax = self.expected_tax()
            
            target_deposits = wages * p.ratio.ratio_of_deposits_to_wage
            max_div_gap = self.history_div_gap()

            finance_3 = finance_2 + wages + div + tax - deposit_interests + target_deposits + max_div_gap
            finance = max(0, finance_1, finance_2, finance_3)

            # 记录下重要的变量
            self.yearly_info[self.year].cfn_investment_amount = investment_amount
            self.yearly_info[self.year].cfn_deposits_at_finance = deposits_holds
            self.yearly_info[self.year].cfn_finance_1 = finance_1

            self.yearly_info[self.year].cfn_consumption_amount = consumption_amount
            self.yearly_info[self.year].cfn_loan_int_pri_repayment = loan_int_pri_repayment
            self.yearly_info[self.year].cfn_finance_2 = finance_2

            self.yearly_info[self.year].cfn_deposit_interests = deposit_interests
            self.yearly_info[self.year].cfn_wages_planed = wages
            self.yearly_info[self.year].cfn_div_planed = div
            self.yearly_info[self.year].cfn_tax_planed = tax
            self.yearly_info[self.year].cfn_max_div_gap = max_div_gap

            self.yearly_info[self.year].cfn_finance_3 = finance_3
            self.yearly_info[self.year].finance = finance
            
            
        return self.yearly_info[self.year].finance

    def k_finance(self):
        # 计算k公司的融资额，要以合同计算，而非预期
        deposits_holds = self.deposits_contracts.amount # 现在有多少存款
        investment_amount = np.sum([c.quantity * c.price for _, c in self.capital_goods_contracts_collection.items()])
        # 第二关，贷款本金利息偿还后为正
        loan_int_pri_repayment = self.cal_interests_payment_of_loans + self.cal_principle_payment_of_loans
        finance_2 = loan_int_pri_repayment - investment_amount - deposits_holds
        # 第三关，获得存款利息，同时交税，发股利，发工资后为正
        deposit_interests = self.deposits_contracts.interest_rate * self.deposits_contracts.amount
        wages = np.sum([jb.wage for _,jb in self.job_contracts_collection.items()])
        div = self.expected_dividends()
        tax = self.expected_tax()

        target_deposits = wages * p.ratio.ratio_of_deposits_to_wage
        finance_3 = finance_2 + wages + div + tax - deposit_interests + target_deposits
        return max(0, finance_2, finance_3)


    def record_gap(self):
        self.yearly_info[self.year].workers_gap += 1

    def production(self):
        """
        生产过程，工人把自己劳动力和利用资本（K，如有）转化为商品，公司存货增加y，但此时还没有销售出去
        其生产的量来源于 ConsumptionFirmProduceAndRecruit.y_desired
        :return:
        """
        if self.is_cap_firm:
            workers = len(self.job_contracts_collection)
            output_most = workers * p.para.productivity_of_k_firm
            # 这里存在巨大的隐患，desired一定能实现么？对于k公司来说，当招不到人的时候，真实和desired相差很大。
            y = min(output_most, self.price_and_recruit.y_desired)
            self.yearly_info[self.year].y_real = y
        else:
            # 对于C公司来说，两个限制
            # workers = len()
            y = self.price_and_recruit.y_desired
        record_dic = {
            'uuid': uuid.uuid4(),
            'quantity': y,
            'item': self.ep.produce,
            'transaction_flow_item': f"{self.firm_type} Production",
            'flow': f"{self.firm_type}获得产品,库存增加",
            'deposit_before': self.deposits_contracts.amount,
            'deposit_after': self.deposits_contracts.amount,
        }
        record = Record(uuid.uuid4())
        record.add_more_attr(record_dic)
        self.inventory.quantity += y
        self.log.append(record)

    def delta_deposit(self):
        dep_as_assets, loans_as_debt, capital_as_assets, inventory_mv = self.get_bs_info()
        start_info = self.balance_sheet_info
        return dep_as_assets - start_info['deposits_start']

    def valid_accounting_consistent(self):
        df = pd.DataFrame([s.__dict__ for s in self.log])
        # 居民消费，消费品公司获得的现金
        consumption = df[df['item'] == self.ep_item.cash_flow_consumption]['amount'].sum()
        # 消费品公司的投资
        investment = df[df['item'] == self.ep_item.cash_flow_capital]['amount'].sum()
        # df.to_excel("../df_check.xlsx")
        if self.is_cap_firm:
            wages_item = self.ep_item.k_firm_pay_wage
            loan_interests_item = self.ep_item.k_firm_pay_loan_int
            new_loan_item = self.ep_item.k_firm_new_loan
            dep_interests_item = self.ep_item.bank_pay_dep_int_to_k
            tax_item = self.ep_item.k_firm_pay_tax
            firm_pay_div_item = self.ep_item.k_firm_pay_div
            firm_keep_div_item = self.ep_item.k_firm_keep_div
            repay_loan_item = self.ep_item.k_firm_loan_repayment
            # 不产生现金流项目
            inv_revalue_item = self.ep_item.k_firm_inv_revalue
            capital_amortization = 0
        else:
            wages_item = self.ep_item.c_firm_pay_wage
            loan_interests_item = self.ep_item.c_firm_pay_loan_int
            new_loan_item = self.ep_item.c_firm_new_loan
            dep_interests_item = self.ep_item.bank_pay_dep_int_to_c
            tax_item = self.ep_item.c_firm_pay_tax
            firm_pay_div_item = self.ep_item.c_firm_pay_div
            firm_keep_div_item = self.ep_item.c_firm_keep_div
            repay_loan_item = self.ep_item.c_firm_loan_repayment
            # 不产生现金流项目
            inv_revalue_item = self.ep_item.c_firm_inv_revalue
            capital_amortization = df[df['item'] == self.ep_item.capital_amortization]['amount'].sum()

        wages = df[df['item'] == wages_item]['amount'].sum()
        loan_interests = df[df['item'] == loan_interests_item]['amount'].sum()
        deposit_interests = df[df['item'] == dep_interests_item]['amount'].sum()
        new_loans = df[df['item'] == new_loan_item]['amount'].sum()
        taxes = df[df['item'] == tax_item]['amount'].sum()
        firm_pay_div = df[df['item'] == firm_pay_div_item]['amount'].sum()
        firm_keep_div = df[df['item'] == firm_keep_div_item]['amount'].sum()
        repay_loans = df[df['item'] == repay_loan_item]['amount'].sum()
        # 不产生现金流项目
        change_inventory = df[df['item'] == inv_revalue_item]['amount'].sum()
        dividends = firm_pay_div + firm_keep_div

        if self.is_cap_firm:
            sum_in = investment + deposit_interests + new_loans
            sum_out = loan_interests + wages + taxes + repay_loans + firm_pay_div
        else:
            sum_in = consumption + deposit_interests + new_loans
            sum_out = loan_interests + wages + investment + taxes + repay_loans + firm_pay_div

        dep_as_assets, loans_as_debt, capital_as_assets, inventory_mv = self.get_bs_info()
        start_info = self.balance_sheet_info

        start_net_worth = start_info['deposits_start'] + start_info['inventory_mv_start'] + \
            start_info['capital_start'] -start_info['loans_start']
        net_worth = dep_as_assets + inventory_mv + capital_as_assets - loans_as_debt
        
        bs_table = {
            '存款': [start_info['deposits_start'], dep_as_assets, dep_as_assets-start_info['deposits_start']],
            '存货': [start_info['inventory_mv_start'], inventory_mv, inventory_mv-start_info['inventory_mv_start']],
            '资产': [start_info['capital_start'], capital_as_assets, capital_as_assets-start_info['capital_start']],
            '贷款': [-start_info['loans_start'], -loans_as_debt, -loans_as_debt+start_info['loans_start']],
            '净值': [start_net_worth, net_worth, net_worth-start_net_worth]
        }

        dic_bs_to_ck = {
            '流入：消费收入': [consumption],
            '流入：存款利息': [deposit_interests],
            '流入：新增贷款': [new_loans],
            '流入：汇总项':[sum_in],
            '流出：工资':[-wages],
            '流出：投资':[-investment],
            '流出：贷款利息':[-loan_interests],
            '流出：偿还贷款':[-repay_loans],
            '流出：交税':[-taxes],
            '流出：股东红利':[-firm_pay_div],
            '流出：汇总项':[-sum_out],
            '净流入流出（正代表流入）':[sum_in-sum_out]
        }
        if self.is_cap_firm:
            dic_ck = {
                self.tf_item.Investments: [investment, 0],
                self.tf_item.Wages: [-wages,0],
                self.tf_item.CG_on_inventories:[change_inventory,-change_inventory],
                self.tf_item.Taxes: [-taxes, 0],
                self.tf_item.Dep_interest:[deposit_interests, 0],
                self.tf_item.Loans_interest:[-loan_interests, 0],
                self.tf_item.Profits:[-dividends, firm_keep_div],
                self.tf_item.delta_deposits:[0,0-self.delta_deposit()],
                self.tf_item.delta_loans:[0, new_loans-repay_loans],
            }
        else:
            dic_ck = {
                self.tf_item.Consumption: [consumption,0],
                self.tf_item.Wages: [-wages,0],
                self.tf_item.CG_on_inventories:[change_inventory,-change_inventory],
                self.tf_item.Investments:[0, -investment],
                self.tf_item.Capital_amortization:[-capital_amortization,capital_amortization],
                self.tf_item.Taxes: [-taxes, 0],
                self.tf_item.Dep_interest:[deposit_interests, 0],
                self.tf_item.Loans_interest:[-loan_interests, 0],
                self.tf_item.Profits:[-dividends, firm_keep_div],
                self.tf_item.delta_deposits:[0,0-self.delta_deposit()],
                self.tf_item.delta_loans:[0, new_loans-repay_loans],
            }
        df_c = pd.DataFrame(dic_ck)
        df_c.index = ['Con.CA', 'Con.KA']
        df_c['Sum'] = df_c.sum(axis=1)
        self.transaction_flow_table = df_c.T
        self.transaction_flow_table.round(2)

        df_bs_to_ck = pd.DataFrame(dic_bs_to_ck)
        df_bs_to_ck.index = ['v']
        self.check_deposit_table = df_bs_to_ck.T.round(2)

        df_bs_table = pd.DataFrame(bs_table)
        df_bs_table.index = ['期初', '期末', '变动']
        df_bs_table['sum'] = df_bs_table.sum(axis=1)
        self.balance_sheet_table = df_bs_table.T.round(2)
        self.balance_sheet_table.round(2)

        delta_deposit_bs = dep_as_assets-start_info['deposits_start']
        delta_deposit_tf = sum_in - sum_out

        if self.transaction_flow_table.iloc[-1,0] > 0.01 or self.transaction_flow_table.iloc[-1,0] < -0.01:
            print("公司的资金流量表配不平")
            print(self.transaction_flow_table)

        if abs(delta_deposit_bs - delta_deposit_tf) > 0.01:
            print('error')

        assert delta_deposit_bs - delta_deposit_tf < 0.01

    def series_bs_tf_item(self, table_name, index_name, col_name):
        """AI is creating summary for series_bs_tf_item

        Args:
            table_name ([type]): [资产负债表 资金流量表 存款校验表 生产决策过程]
            index_name ([type]): [description]
            col_name ([type]): [description]

        Returns:
            [type]: [description]
        """
        # table_name = '资产负债表'
        # index_name = '存款'
        # col_name = '期末'
        new_dic = {year:dic[table_name].loc[index_name, col_name] for year, dic in self.df_year_log.items()}
        se = pd.Series(new_dic)
        return se

    def bs_df_to_2d(self):
        collection = []
        self.save_bs_tf_table()
        if len(self.df_year_log) > 0:
            key_first = list(self.df_year_log.keys())[0]
            for table_name in self.df_year_log[key_first].keys():
                df = self.df_year_log[key_first][table_name]
                for idx in df.index:
                    if isinstance(df, pd.DataFrame):
                        for col in df.columns:
                            se = self.series_bs_tf_item(table_name, idx, col)
                            se.name = f"{table_name}_{idx}_{col}"
                            collection.append(se)
            df_year_log = pd.concat(collection, axis=1)
            df_yearly_text = pd.DataFrame(self.yearly_info_text)
            df_info = pd.DataFrame({year:v.__dict__ for year, v in self.yearly_info.items()})
            return df_year_log, df_yearly_text, df_info
        else:
            print(666)
    
    def save_bs_tf_table(self):
        dir_h = self.ensure_dir_before_save()
        # file_path = f"{dir_h}{self.year}资产负债资金流量.xlsx"
        produce_and_decision = pd.Series(self.price_and_recruit.info)
        produce_and_decision.name = self.year
        # 资金流量表的结构
        dic = {'资产负债表': self.balance_sheet_table,
                '资金流量表': self.transaction_flow_table,
                '存款校验表': self.check_deposit_table,
                '生产决策过程': produce_and_decision}
        self.df_year_log[self.year] = dic

    def save_self_history_log(self):
        df_year_log, df_yearly_text, df_info = self.bs_df_to_2d()
        with pd.ExcelWriter(f"../log/{self.id_()}df_info_all.xlsx") as writer_info:
            df_info.T.to_excel(writer_info, 'info')
            df_year_log.to_excel(writer_info, 'log')
            df_yearly_text.T.to_excel(writer_info, 'text')
        
        log = self.df_year_log
        save_excel_names = ['资产负债表', '资金流量表']
        for key in save_excel_names:
            excel_file_path = f"../log/{self.id_()}_{key}.xlsx"
            with pd.ExcelWriter(excel_file_path) as writer:
                for year in log.keys():
                    df = log[year][key]
                    a, b = -0.0001, 0.0001
                    df[(df >= a) & (df <= b)] = 0
                    df.round(3).to_excel(writer, sheet_name=f"{year}")


class HouseHolds:
    def __init__(self, ini_id):
        # 由家庭向公司出售劳动力以换取工资，
        # 以银行存款的形式消费和储蓄。
        # 家庭按其财富比例拥有公司和银行，并
        # 从公司和银行的利润中获得一部分作为股息。
        # 失业工人从政府获得救济金。
        # 最后，家庭为其总收入纳税。
        self.year = INI_YEAR
        self.identity = 'household'
        self.household_id = ini_id  # 如何初始化id，不能重复
        self.job_contract: JobContract = JobContract({})  # 后续
        self.stockholder_contract: Dict[str, StockHolderContract] = {}
        self.deposits_contracts: DepositContract = DepositContract(contract={})  # 后续在CreditMarket里面根据贷款合同赋值

        # Bug修复：将 {} 改为 ConsumptionContract(contract={})，以正确初始化 ConsumptionContract 实例
        self.consumption_contract: ConsumptionContract = ConsumptionContract(contract={})
        self.yearly_info = {INI_YEAR: HouseholdInfo(ini_id)}
        self.log = []

    def id_(self):
        return self.household_id

    def reset_state(self, save_log=False):
        if save_log:
            self.save_log()
        self.yearly_info[self.year] = HouseholdInfo(self.id_())
        self.log = []

    def ensure_dir_before_save(self):
        dir_ = p.ctrl.log_dir
        ensure_folder_exists(dir_)
        dir_h = f"{dir_}{self.id_()}/"
        ensure_folder_exists(dir_h)
        return dir_h

    def save_log(self):
        dir_h = self.ensure_dir_before_save()
        df = pd.DataFrame(self.log)
        df.to_csv(f"{dir_h}{self.year}.csv", index=False)

    def pay(self, money: float, record: Record):
        record.deposit_before = self.deposits_contracts.amount
        if self.deposits_contracts.amount < money:
            print("error, 居民不能付其没有的钱")
        self.deposits_contracts.amount -= money  # 用存款付钱
        record.year = self.year
        record.deposit_after = self.deposits_contracts.amount
        self.log.append(record)

    def accept_payment(self, money: float, record: Record):
        self.deposits_contracts.amount += money  # 用存款收钱
        record.year = self.year
        record.trade = 'accept_payment'
        record.deposit_after = self.deposits_contracts.amount
        self.log.append(record)

    def update_wages_contract(self, rate_of_unemployment):
        # 居民和企业(雇主)重新议价
        # 如果居民一年内四期中两个季度（期）都没有找到工作，那么其降低自己的工资要求
        # 如果居民发现物价上涨超过一定的阈值，那么就要求涨工资以满足自身的购买力
        rd_value = abs(np.random.normal(p.a.normal_d_mean, p.a.normal_d_dev))  # 得到一个随机数
        wage = self.job_contract.wage
        employed = self.job_contract.employed
        if not employed[-1] and not employed[-2]:  # 有两期失业
            wage = wage * (1 - rd_value)
        elif rate_of_unemployment < p.ratio.unemployment_rate_target:  # 总体失业率低于目标值
            wage = wage * (1 + rd_value)
        info = self.yearly_info[self.year]
        info.wage = wage
        return wage

    @property
    def stock_market_value(self):
        return 0

    @property
    def deposits(self):
        return self.deposits_contracts.amount

    @property
    def wealth(self):
        return self.stock_market_value + self.deposits

    def record_deposits_interests(self, v):
        self.yearly_info[self.year].deposits_interests = v
    
    def record_dividends(self, v):
        if not hasattr(self.yearly_info[self.year],'dividends'):
            self.yearly_info[self.year].dividends = v
        else:
            self.yearly_info[self.year].dividends += v

    def expected_gross_income(self):
        if self.job_contract.employed[-1]:
            wage = self.job_contract.wage
        else:
            wage = self.job_contract.dole
        # 在收到股利的时候，记录下来的
        deposits_interests = self.yearly_info[self.year-1].deposits_interests
        dividends_recived = self.yearly_info[self.year-1].dividends
        expected_gross_income = wage + deposits_interests + dividends_recived
        self.yearly_info[self.year].expected_gross_income = expected_gross_income
        return expected_gross_income

    def consume(self, price):
        """
        居民消费决定方程
        """
        if price == 0:
            print(f"price is wrong")
        if self.year == INI_YEAR:
            q = (p.y.output_of_consumption / p.w.number_of_households) * (1 + p.ss.steady_state_rate)
            self.yearly_info[self.year].consumption_price = price
            self.yearly_info[self.year].consumption_quantiy = q
            return 0, 0, 0, q
        else:
            expected_gross_income = self.expected_gross_income()
            expected_net_income = expected_gross_income * (1 - p.ratio.tax_rate)
            
            assert price > 0

            a1 = expected_net_income * p.h.households_consumption_para_1 / price
            a2 = self.deposits * p.h.households_consumption_para_2 / price
            # assert a1 + a2 > 0
            self.yearly_info[self.year].a1 = a1
            self.yearly_info[self.year].a2 = a2

            max_q = 0.99 * self.deposits_contracts.amount / price
            min_q = 0.29 * self.deposits_contracts.amount / price
            self.yearly_info[self.year].max_q = max_q
            self.yearly_info[self.year].min_q = min_q

            self.yearly_info[self.year].consumption_price = price
            q = min(max_q, a1 + a2)
            q = max(min_q, q)

            self.yearly_info[self.year].consumption_quantiy = q
            return a1, a2, max_q, q


class Banks:
    def __init__(self, ini_id, ini_bonds, cash_advance_contract, reserve_contract):
        self.tf_item:TransactionFlowItem = TransactionFlowItem()
        self.ep_item:EpisodeItem = EpisodeItem()
        self.identity = 'bank'
        self.year = INI_YEAR
        self.bank_id = ini_id
        self.yearly_info = {INI_YEAR:BankInfo(ini_id)}
        self.capital_ratio_targets = p.b.capital_ratio_target  # 流动比率 NW / Loan
        self.liquidity_ratio_target = p.b.mandatory_liquidity_ratio
        self.loan_interest_rate = p.ir.interest_rate_of_loans  # 贷款利率
        self.deposit_interest_rate = p.ir.interest_rate_of_deposits  # 存款利率
        self.cash_advance_interest_rate = p.ir.interest_rate_of_advances  # cash advance rate 银行向央行短期借款利率
        self.reserve_contract: ReserveContract = reserve_contract
        # 贷款和贷款合约
        self.loans_contracts: Dict[str, LoanContractCollection] = {}
        self.stockholder_contract: Dict[str, StockHolderContract] = {}
        # 债券和债券合约
        self.bonds = ini_bonds  # 银行持有的政府债券
        self.bonds_contracts: List[BondContract] = []
        # 银行负债
        self.deposits_contracts = {}
        self.cash_advance_contracts:CashAdvanceContract = cash_advance_contract  # 银行向中央银行的预借款
        # 比率
        self.non_performing_loans = 0  # 违约贷款
        self.log = []
        self.para = {}
        self.balance_sheet_info = {}

    def id_(self):
        return self.bank_id

    def reset_state(self):
        self.yearly_info[self.year] = BankInfo(bank_id=self.id_())
        bond_amount = np.sum([bond.amount for bond in self.bonds_contracts]) 
        self.balance_sheet_info['deposits_start'] = self.deposits
        self.balance_sheet_info['loans_start'] = self.loans
        self.balance_sheet_info['bonds_start'] = bond_amount
        self.balance_sheet_info['reserve_start'] = self.reserve_contract.amount
        self.balance_sheet_info['cash_advance_start'] = self.cash_advance_contracts.amount
        print(self.id_(), self.balance_sheet_info)
        self.log = []

    @property
    def net_wealth(self):
        if len(self.deposits_contracts) > 1:
            return self.reserve_contract.amount + self.loans + self.bonds - self.deposits - \
                   self.cash_advance_contracts.amount  # 净资产
        else:
            print("现在无法计算银行净资产")

    @property
    def deposits(self):
        return np.sum([c.amount for _, c in self.deposits_contracts.items()])

    @property
    def loans(self):
        info = self.yearly_info[self.year]
        if not hasattr(info, 'loans'):
            v = np.sum([loan.cal_loans_as_debt() for _, loan in self.loans_contracts.items()])
            info.loans = v
        return self.yearly_info[self.year].loans
    
    def pay(self, money: float, record: Record):
        """
        银行付款，通过其reserves的减少
        """
        record.add_attr('reserve_before', self.reserve_contract.amount)
        self.reserve_contract.amount -= money
        record.add_attr('reserve_after', self.reserve_contract.amount)
        record.add_attr('reserve_outflow', money)
        self.log.append(record)

    def accept_payment(self, money: float, record: Record):
        """
        银行收款，通过reserves增加
        """
        record.add_attr('reserve_before', self.reserve_contract.amount)
        self.reserve_contract.amount += money
        record.add_attr('reserve_after', self.reserve_contract.amount)
        record.add_attr('reserve_inflow', money)
        self.log.append(record)

    def set_loan_interest_rate(self):
        """
        当银行 Capital_ratio(net_wealth / loan)低于目标值,意味着贷款过多，则需要降低贷款，
        这时候贷款利率随机增加。当其高于目标值，意味着可放贷的空间比较大，这时候贷款随机利率降低。
        :return:
        """
        rd_value = abs(np.random.normal(p.a.normal_d_mean, p.a.normal_d_dev))

        if self.calculate_capital_ratio() > self.capital_ratio_targets:
            self.loan_interest_rate = self.loan_interest_rate * (1 + rd_value)
        else:
            self.loan_interest_rate = self.loan_interest_rate * (1 - rd_value)

    def set_deposit_interest_rate(self):
        """
        银行设定存款利率，如果流动资金比率，表示为存款准备金/存款，高于目标值，则需要降低存款利率
        当liquidity ratio低于目标设定，银行将存款利率随机增加一个值（正态分布）
        :return:
        """
        rd_value = np.random.normal(p.a.normal_d_mean, p.a.normal_d_dev)
        old_deposit_interest_rate = self.deposit_interest_rate
        if self.calculate_liquidity_ratio() >= self.liquidity_ratio_target:
            self.deposit_interest_rate = self.deposit_interest_rate * (1 + rd_value)
            text = '上调利率'
        else:
            self.deposit_interest_rate = self.deposit_interest_rate * (1 - rd_value)
            text = '下调利率'
        if self.bank_id == 'bank9':
            apt(f"银行{self.bank_id}重新{text}为:{self.deposit_interest_rate:.6f}, 原始为{old_deposit_interest_rate:.6f}")

    def cal_average_loan_interest_rate(self):
        """
        计算贷款加权平均利率
        :return:
        """

    def calculate_liquidity_ratio(self):
        return self.reserve_contract.amount / self.deposits

    def calculate_capital_ratio(self):
        return self.net_wealth / self.loans

    def bond_interests_revenue(self):
        info = self.yearly_info[self.year]
        if not hasattr(info, 'bond_interests_revenue'):
            item = self.ep_item.gov_pay_int_to_bank
            v = np.sum([d.amount for d in self.log if d.item == item])
            info.bond_interests_revenue = v
        return self.yearly_info[self.year].bond_interests_revenue

    def c_loan_interests_revenue(self):
        info = self.yearly_info[self.year]
        if not hasattr(info, 'c_loan_interests_revenue'):
            c_item = self.ep_item.c_firm_pay_loan_int
            # 计算企业支付贷款利息的总金额
            c_interests = np.sum([d.amount for d in self.log if d.item == c_item])
            info.c_loan_interests_revenue = c_interests
        return self.yearly_info[self.year].c_loan_interests_revenue

    def k_loan_interests_revenue(self):
        info = self.yearly_info[self.year]
        if not hasattr(info, 'k_loan_interests_revenue'):
            k_item = self.ep_item.k_firm_pay_loan_int
            # 计算企业支付贷款利息的总金额
            k_interests = np.sum([d.amount for d in self.log if d.item == k_item])
            info.k_loan_interests_revenue = k_interests
        return self.yearly_info[self.year].k_loan_interests_revenue

    def loan_interests_revenue(self):
        info = self.yearly_info[self.year]
        if not hasattr(info, 'loan_interests_revenue'):
            c_interests = self.c_loan_interests_revenue()
            k_interests = self.k_loan_interests_revenue()
            # 返回企业支付贷款利息和银行支付贷款利息的总和
            info.loan_interests_revenue = c_interests + k_interests
        return self.yearly_info[self.year].loan_interests_revenue
    
    def c_deposit_interests_cost(self):
        info = self.yearly_info[self.year]
        if not hasattr(info, 'c_deposit_interests_cost'):
            c_item = self.ep_item.bank_pay_dep_int_to_c
            c_interests = np.sum([d.amount for d in self.log if d.item == c_item])
            info.c_deposit_interests_cost = c_interests
        return self.yearly_info[self.year].c_deposit_interests_cost

    def k_deposit_interests_cost(self):
        info = self.yearly_info[self.year]
        if not hasattr(info, 'k_deposit_interests_cost'):
            k_item = self.ep_item.bank_pay_dep_int_to_k
            k_interests = np.sum([d.amount for d in self.log if d.item == k_item])
            info.k_deposit_interests_cost = k_interests
        return self.yearly_info[self.year].k_deposit_interests_cost

    def h_deposit_interests_cost(self):
        info = self.yearly_info[self.year]
        if not hasattr(info, 'h_deposit_interests_cost'):
            h_item = self.ep_item.bank_pay_dep_int_to_h
            h_interests = np.sum([d.amount for d in self.log if d.item == h_item])
            info.h_deposit_interests_cost = h_interests
        return self.yearly_info[self.year].h_deposit_interests_cost

    def deposit_interests_cost(self):
        """
        支付存款利息，包括c公司存款利息，k公司存款利息，households存款利息
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'deposit_interests_cost'):
            c_interests = self.c_deposit_interests_cost()
            k_interests = self.k_deposit_interests_cost()
            h_interests = self.h_deposit_interests_cost()
            info.deposit_interests_cost = c_interests + k_interests + h_interests
        return self.yearly_info[self.year].deposit_interests_cost

    @property
    def profits(self):
        """
        计算利润，利润 = （债券利息收入 + 贷款利息收入） - （存款利息支出 + 预借款利息支出）
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'profits'):
            revenue = self.bond_interests_revenue() + self.loan_interests_revenue()
            cost = self.deposit_interests_cost() + self.cash_advance_interests()
            info.profits = revenue - cost
        return self.yearly_info[self.year].profits

    def tax(self):
        """
        计算要缴的税，税收 = 税率 × 利润，（如果利润为负，税为零）
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'tax'):
            # 当利润为正的时候，税收与税成正比
            # 当利润为负的时候，税收为零
            if self.profits > 0:
                tax = self.profits * p.ratio.tax_rate
            else:
                tax = 0
            info.tax = tax
        return self.yearly_info[self.year].tax

    def after_tax_profits(self):
        """
        计算税收利润，结果一定大于等于零
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'after_tax_profits'):
            if self.profits > 0:
                after_tax_profits = self.profits * (1 - p.ratio.tax_rate)
            else:
                after_tax_profits = 0
            info.after_tax_profits = after_tax_profits
        return self.yearly_info[self.year].after_tax_profits

    @property
    def dividends(self):
        """
        计算股东利润，等于税后利润 × 分红比例
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'dividends'):
            after_tax_profits = self.after_tax_profits()
            ratio = p.ratio.ratio_of_dividends_to_stockholder_as_share_of_profits_of_bank
            info.dividends = ratio * after_tax_profits
        return self.yearly_info[self.year].dividends
        
    def keep_profits(self):
        """
        计算股东留存利润，如果税后利润为正，其为正，等于税后利润 × 存留比例
        存留比例等于 1 - 分红比例
        如果税收利润为负值，其为在资本账户体现为负值，弥补经营性亏损，数值就等于利润
        这相当于经营性亏损，要通过资本账户虚拟出一笔钱（没有真实资金流量）来弥补经营亏损
        在经常账户体现为正值
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'keep_profits'):
            if self.profits > 0:
                after_tax_profits = self.profits * (1 - p.ratio.tax_rate)
                ratio = p.ratio.ratio_of_dividends_to_profits_left_in_bank
            else:
                after_tax_profits = self.profits
                ratio = 1
            # 这里如果profits为负，keep profits 要在资本账户弥补亏损
            info.keep_profits = ratio * after_tax_profits
        return self.yearly_info[self.year].keep_profits

    def cash_advance_interests(self):
        """
        现金预借款利息，= 现金预借款 × 利息
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'advance_interests'):
            interest_amount = self.cash_advance_contracts.interest_rate * \
                self.cash_advance_contracts.amount
            info.advance_interests = interest_amount
        return self.yearly_info[self.year].advance_interests

    def available_money_for_buy_bonds(self):
        """
        本银行能够用于购买债券的钱
        banks use their reserves in excess of their target (after repayment of previous bonds by the
        government) to buy government bonds.
        """
        # 准备金
        target_reserves = self.deposits * p.ratio.bank_min_reserve_rate
        available_money = max(0, self.reserve_contract.amount - target_reserves)
        return available_money

    def ask_for_cash_advance(self):
        """
        本函数是available_money_for_buy_bonds的镜像对称，如果reserves多了，就可以买债券，如果少了就要借
        """
        target_reserves = self.deposits * p.b.mandatory_liquidity_ratio
        if target_reserves > self.reserve_contract.amount:
            return target_reserves - self.reserve_contract.amount
        else:
            return 0

    def __str__(self):
        str_ = ''
        for atr in ['bank_id', 'loan_interest_rate', 'deposit_interest_rate', 'reserves', 'loans', 'bonds', 'deposits']:
            value = self.__dict__[atr]
            str_ += f">银行Agent属性>【{atr}={value}】\n"
        return str_

    def valid_accounting_consistent(self):
        # 银行检查会计一致性，如何检查
        # 银行业务收入，贷款利息，持有的债券利息
        # 银行业务支出，存款利息
        # 银行利润计算 = 收入 - 支出
        # 银行应交税 = 银行利润 * 税率
        df = pd.DataFrame([s.__dict__ for s in self.log])
        # ————————————————————————————————————经常账户————————————————————————————————————————————
        info = self.yearly_info[self.year]
        # 贷款利息
        loan_interests = info.c_loan_interests_revenue + info.k_loan_interests_revenue
        # advance 利息
        advance_interests = info.advance_interests
        # 债券利息
        bond_interests = info.bond_interests_revenue
        # 存款利息支出
        dep_interests = info.c_deposit_interests_cost + info.k_deposit_interests_cost + \
            info.h_deposit_interests_cost
        # 交税
        tax = info.tax
        # 发放股利
        stockholder_div = info.dividends
        # ————————————————————————————————————————————————————————————————————————————————————
        # 资本账户
        # 存留股利
        keep_div = info.keep_profits
        # ---------负债的变动----------
        # 存款变动
        # delta_deposits = self.sum_item_by_ep_item(df, self.ep_item.)
        deposits_end = np.sum([c.amount for _, c in self.deposits_contracts.items()])
        delta_deposits = deposits_end - self.balance_sheet_info['deposits_start']
        # ---------资产的变动----------
        # 贷款变动
        loan_end = np.sum([loan.cal_loans_as_debt() for _, loan in self.loans_contracts.items()])
        delta_loans = loan_end - self.balance_sheet_info['loans_start']
        # 债券变动
        bonds_end = np.sum([bond.amount for bond in self.bonds_contracts])
        delta_bonds = bonds_end - self.balance_sheet_info['bonds_start']
        # reserve
        reserves_end = self.reserve_contract.amount
        delta_reserves = reserves_end - self.balance_sheet_info['reserve_start']
        # cash advance 变动
        cash_advance_end = self.cash_advance_contracts.amount
        delta_cash_advance = cash_advance_end - self.balance_sheet_info['cash_advance_start']

        dic_bank = {
            # 7.Taxes
            self.tf_item.Taxes:[-tax, 0],
            # 8.dep
            self.tf_item.Dep_interest:[-dep_interests, 0],
            # 9.Bonds interest
            self.tf_item.Bonds_interest:[bond_interests, 0],
            # 10.Loans interest
            self.tf_item.Loans_interest:[loan_interests, 0],
            self.tf_item.Advances_interest:[-advance_interests, 0],
            self.tf_item.Profits:[-(stockholder_div + keep_div), keep_div],
            self.tf_item.delta_deposits:[0, delta_deposits],
            self.tf_item.delta_advances:[0, delta_cash_advance],
            self.tf_item.delta_reserves:[0, -delta_reserves],
            self.tf_item.delta_Gov_bonds:[0, -delta_bonds],
            self.tf_item.delta_loans:[0, -delta_loans],
        }
        df = pd.DataFrame(dic_bank)
        df.index = ['Banks CA', 'Banks KA']
        df['Sum'] = df.sum(axis=1)
        a, b = -0.01, 0.01
        df[(df >= a) & (df <= b)] = 0

        self.transaction_flow_table = df.T
        # ddf = pd.DataFrame([s.__dict__ for s in self.log])
        # print(self.transaction_flow_table.round(2))

        se = self.transaction_flow_table.iloc[-1,:]
        # for v in se:
        #     # if abs(v) >= 0.01:
        #     #     print(self.transaction_flow_table)
        #     assert abs(v) < 0.01
        return self.transaction_flow_table.round(2)


class Government:
    def __init__(self, gov_deposit: TreasuryDepositContract):
        self.year = INI_YEAR
        self.identity = 'government'
        self.ep_item = EpisodeItem()
        self.treasury_deposit_contract: TreasuryDepositContract = gov_deposit  # check
        self.job_contracts_collection: Dict[str,JobContract] = {}  # check
        self.bonds_collection: List[BondContract] = []  # check一批债券集合
        self.bonds = p.g.bonds_government_issue  # 政府负债
        self.has_collected_tax = False
        self.has_paid_wages = False
        self.has_paid_doles = False
        self.has_paid_bonds_int = False
        self.yearly_info:Dict[str,GovernmentInfo] = {INI_YEAR:GovernmentInfo()}
        self.log = []

    @staticmethod
    def id_():
        return 'gov'

    def pay(self, money: float, record: Record):
        # assert self.treasury_deposit_contract.amount >= money
        self.treasury_deposit_contract.amount -= money
        self.log.append(record)

    def accept_payment(self, money: float, record: Record):
        self.treasury_deposit_contract.amount += money
        self.log.append(record)

    def reset_state(self):
        """
        episode start run
        """
        self.yearly_info[self.year] = GovernmentInfo()
        self.treasury_deposit_start()
        self.bonds_start()
        self.has_collected_tax = False
        self.has_paid_wages = False
        self.has_paid_doles = False
        self.has_paid_bonds_int = False
        self.log = []
    
    def treasury_deposit_start(self):
        """
        这个函数一定要在episode开始的时候运行一下,
        treasury deposit 是央行的负债,政府的存款
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'treasury_deposit_start'):
            info.treasury_deposit_start = self.treasury_deposit_contract.amount
        return self.yearly_info[self.year].treasury_deposit_start
    
    def list_unemployed_id_of_households(self):
        return [h_id for h_id, jb in self.job_contracts_collection.items() if not jb.employed[-1]]

    def c_firms_tax_amounts(self):
        """
        计算消费品公司了多少税
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'c_firms_tax_amounts'):
            item = self.ep_item.c_firm_pay_tax
            tax = np.sum([d.amount for d in self.log if d.item == item])
            info.c_firms_tax_amounts = tax
        return self.yearly_info[self.year].c_firms_tax_amounts

    def k_firms_tax_amounts(self):
        """
        计算资本品公司了多少税
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'k_firms_tax_amounts'):
            item = self.ep_item.k_firm_pay_tax
            tax = np.sum([d.amount for d in self.log if d.item == item])
            info.k_firms_tax_amounts = tax
        return self.yearly_info[self.year].k_firms_tax_amounts

    def banks_tax_amounts(self):
        """
        银行缴了多少税
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'banks_tax_amounts'):
            item = self.ep_item.bank_pay_tax
            tax = np.sum([d.amount for d in self.log if d.item == item])
            info.banks_tax_amounts = tax
        return self.yearly_info[self.year].banks_tax_amounts

    def households_tax_amounts(self):
        """
        居民缴了多少税
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'households_tax_amounts'):
            item = self.ep_item.household_pay_tax
            tax = np.sum([d.amount for d in self.log if d.item == item])
            info.households_tax_amounts = tax
        return self.yearly_info[self.year].households_tax_amounts

    def tax_amounts(self):
        info = self.yearly_info[self.year]
        if not hasattr(info, 'tax_amounts'):
            c_tax = self.c_firms_tax_amounts()
            k_tax = self.k_firms_tax_amounts()
            b_tax = self.banks_tax_amounts()
            h_tax = self.households_tax_amounts()
            tax = c_tax + k_tax + b_tax + h_tax
            info.tax_amounts = tax
        return self.yearly_info[self.year].tax_amounts

    def wages_amounts(self):
        info = self.yearly_info[self.year]
        if not hasattr(info, 'wages_amounts'):
            item = self.ep_item.gov_pay_wage
            info.wages_amounts = np.sum([d.amount for d in self.log if d.item==item])
        return self.yearly_info[self.year].tax_amounts
    
    def doles_amounts(self):
        info = self.yearly_info[self.year]
        if not hasattr(info, 'doles_amounts'):
            item = self.ep_item.gov_pay_dole
            info.doles_amounts = np.sum([d.amount for d in self.log if d.item==item])
        return self.yearly_info[self.year].doles_amounts

    def gov_pay_bank_bond_interests(self):
        assert self.has_paid_bonds_int
        info = self.yearly_info[self.year]
        if not hasattr(info, 'gov_pay_int_to_bank'):
            item = self.ep_item.gov_pay_int_to_bank
            bank_bonds_int = np.sum([d.amount for d in self.log if d.item == item])
            info.gov_pay_int_to_bank = bank_bonds_int
        return self.yearly_info[self.year].gov_pay_int_to_bank

    def gov_pay_cb_bank_bond_interests(self):
        assert self.has_paid_bonds_int
        info = self.yearly_info[self.year]
        if not hasattr(info, 'gov_pay_int_to_cb_bank'):
            item = self.ep_item.gov_pay_int_to_cb_bank
            cb_bank_bonds_int = np.sum([d.amount for d in self.log if d.item == item])
            info.gov_pay_int_to_cb_bank = cb_bank_bonds_int
        return self.yearly_info[self.year].gov_pay_int_to_cb_bank

    def bond_interests_amounts(self):
        assert self.has_paid_bonds_int
        info = self.yearly_info[self.year]
        if not hasattr(info, 'bond_interests_amounts'):
            bank_bonds_int = self.gov_pay_bank_bond_interests()
            cb_bank_bonds_int = self.gov_pay_cb_bank_bond_interests()
            info.bond_interests_amounts = bank_bonds_int + cb_bank_bonds_int
        return self.yearly_info[self.year].gov_pay_int_to_cb_bank

    def receive_cb_profits(self):
        info = self.yearly_info[self.year]
        if not hasattr(info, 'cb_profits'):
            item = self.ep_item.cb_bank_transfer_profits
            v = np.sum([d.amount for d in self.log if d.item == item])
            info.cb_profits = v
        return self.yearly_info[self.year].cb_profits

    def gov_need_issue_bonds(self):
        info = self.yearly_info[self.year]
        if not hasattr(info, 'gov_need_issue_bonds'):
            assert self.has_collected_tax
            assert self.has_paid_doles
            assert self.has_paid_wages
            tax = self.tax_amounts() # 收的税
            cb_profits = self.receive_cb_profits() # 央行利润转移
            wages = self.wages_amounts() # 发的工资
            doles = self.doles_amounts() # 发的救济金
            bonds_ints = self.bond_interests_amounts() # 债券利息 
            outflow = wages + doles + bonds_ints
            inflow = tax + cb_profits
            gap = outflow - inflow
            # 如果gap为负值，说明inflow 大于outflow，不需要融资
            info.gov_need_issue_bonds = max(gap, 0)
        return self.yearly_info[self.year].gov_need_issue_bonds 
        

    def bonds_start(self):
        """
        这个函数一定要在episode开始的时候运行一下,
        bonds 是央行和银行的资产,政府的存款
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'bonds_start'):
            info.bonds_start = self.bonds_as_debt()
        return self.yearly_info[self.year].bonds_start

    def bonds_as_debt(self):
        return np.sum([b.amount for b in self.bonds_collection])
    
    def delta_bonds(self):
        """
        函数只能运行一次，调用之前看看epsode是不是已经运行了
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'delta_bonds'):
            info.delta_bonds = self.bonds_as_debt() - self.bonds_start()
        return self.yearly_info[self.year].delta_bonds

    def delta_treasury_deposit(self):
        """
        函数只能运行一次，调用之前看看episode是不是已经运行了
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'delta_treasury_deposit'):
            now_amount = self.treasury_deposit_contract.amount
            info.delta_treasury_deposit = now_amount - self.treasury_deposit_start()
        return self.yearly_info[self.year].delta_treasury_deposit


class CentralBank:
    """
    中央银行初始化的时候需要
    """

    def __init__(self, all_banks: List[Banks], treasury_deposit_contract: TreasuryDepositContract):
        self.identity = 'central_bank'
        self.ep_item = EpisodeItem()
        self.year = INI_YEAR
        self.cash_advance_rate = p.ir.interest_rate_of_advances
        self.treasury_deposits_contract: TreasuryDepositContract = treasury_deposit_contract  # 财政存款
        self.reserves_contracts: Dict[str,ReserveContract] = {}  # 保证金存款
        self.cash_advances_contracts: Dict[str,CashAdvanceContract] = {}  # 现金预借款
        self.yearly_info:Dict[str,CentralBankInfo] = {INI_YEAR:CentralBankInfo()}
        self.log = []

        for bank in all_banks:
            self.reserves_contracts[bank.bank_id] = bank.reserve_contract
            self.cash_advances_contracts[bank.bank_id] = bank.cash_advance_contracts

        self.bonds_collection = self.ini_cb_holds_bonds()

    def reset_state(self):

        self.yearly_info[self.year] = CentralBankInfo()
        # 开始的时候一定要调用cash_advances_start
        self.cash_advances_start()
        self.reserves_start()
        self.bonds_start()
        
        self.log = []

    def end_state_update(self):
        self.delta_cash_advances_at_episode_end()
        self.delta_reserves_at_episode_end()
        self.delta_bonds_at_episode_end()

    def reserves_start(self):
        """
        这个函数一定要在episode开始的时候运行一下,
        reserves 是央行的负债
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'reserves_start'):
            info.reserves_start = self.reserves
        return self.yearly_info[self.year].reserves_start

    def cash_advances_start(self):
        """
        这个函数一定要在episode开始的时候运行一下,
        cash_advance 是央行的资产
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'cash_advance_start'):
            info.cash_advance_start = self.cash_advances
        return self.yearly_info[self.year].cash_advance_start

    def bonds_start(self):
        """
        这个函数一定要在episode开始的时候运行一下,
        bonds 是央行的资产
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'bonds_start'):
            info.bonds_start = self.bonds
        return self.yearly_info[self.year].bonds_start

    def delta_cash_advances_at_episode_end(self):
        """
        这个函数只能运行一次，调用之前看看是不是在episode里面已经有,
        如果重复调用，那么第二次的资产负债表不一定和第一次一致
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'delta_cash_advances'):
            v = np.sum([c.amount for _, c in self.cash_advances_contracts.items()])
            info.delta_cash_advances = v - self.cash_advances_start()
        return self.yearly_info[self.year].delta_cash_advances

    def delta_reserves_at_episode_end(self):
        """
        这个函数只能运行一次，调用之前看看是不是在episode里面已经有
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'delta_reserves'):
            info.delta_reserves = self.reserves - self.reserves_start()
        return self.yearly_info[self.year].delta_reserves
    
    def delta_bonds_at_episode_end(self):
        """
        这个函数只能运行一次，调用之前看看是不是在episode里面已经有
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'delta_bonds'):
            info.delta_bonds = self.bonds - self.bonds_start()
        return self.yearly_info[self.year].delta_bonds
    
    def cash_advance_interests(self):
        """
        中央银行收到的cash advance利息
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'cash_advance_interests'):
            item = self.ep_item.cash_advance_int
            info.cash_advance_interests = np.sum([d.amount for d in self.log if d.item == item])
        return self.yearly_info[self.year].cash_advance_interests
    
    def bonds_interests(self):
        """
        中央银行收到的债券利息
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'bonds_interests'):
            item = self.ep_item.gov_pay_int_to_cb_bank
            info.bonds_interests = np.sum([d.amount for d in self.log if d.item == item])
        return self.yearly_info[self.year].bonds_interests

    def profits(self):
        """
        中央银行的利润来源1、银行cash advance 利息，2、债券利息
        """
        info = self.yearly_info[self.year]
        if not hasattr(info, 'profits'):
            info.profits = self.bonds_interests() + self.cash_advance_interests()
        return self.yearly_info[self.year].profits
        
    @property
    def cash_advances(self):
        return np.sum([c.amount for _, c in self.cash_advances_contracts.items()])

    @property
    def bonds(self):
        return np.sum([contract.amount for contract in self.bonds_collection])

    @property
    def reserves(self):
        return np.sum([contract.amount for _, contract in self.reserves_contracts.items()])

    @staticmethod
    def ini_cb_holds_bonds() -> list:
        bonds_collection = []
        cb_holds_bonds = p.g.bonds_government_issue - p.b.bank_hold_bonds
        cb_bank_average_amount = cb_holds_bonds / p.para.duration_or_loan
        for i in range(int(p.para.duration_or_loan)):
            cb_contract = {'amount': cb_bank_average_amount,
                           'interest_rate': p.ir.interest_rate_of_bonds,
                           'issue_year': INI_YEAR - i,
                           'remaining_year': p.para.duration_or_loan - i}
            cb_bonds_contracts = BondContract(cb_contract)
            bonds_collection.append(cb_bonds_contracts)
            print('   > 取一份国债合约，其样式为', cb_bonds_contracts)

        print('   >', f"一家中央银行, 持有国债{cb_holds_bonds:.2f}, 分成{p.para.duration_or_loan}期,")
        
        return bonds_collection


if __name__ == "__main__":
    print(666)
